/*=========================================================================
 *
 *  Copyright Insight Software Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#include "itkVTKImageIO.h"

#include "itkImageFileWriter.h"
#include "itkImageFileReader.h"
#include "itkTestingMacros.h"
#include "itkMath.h"


static unsigned int m_CallNumber;

constexpr unsigned int TEST_VECTOR_PIXEL_DIM = 3;

template <typename TPixelType, unsigned int VImageDimension>
class VTKImageIOTester
{
public:

  virtual int Test(int argc, char* argv[] );

  virtual ~VTKImageIOTester()= default;

  static std::string
  SetupFileName( const std::string &filePrefix,
                 const std::string &fileExtension,
                 std::string &outputPath )
    {
    std::ostringstream m_NameWithIndex;
    m_NameWithIndex << filePrefix << "_" << m_CallNumber << "." << fileExtension;

    std::ostringstream m_OutputFileName;

#if defined(WIN32) // windows
    // if it ends in \\ just append the name
    if (outputPath[outputPath.size()-1] == '\\')
      {
      m_OutputFileName << outputPath << m_NameWithIndex.str();
      }
    else
      {
      m_OutputFileName << outputPath << "\\" << m_NameWithIndex.str();
      }

#else /// POSIX UNIX
    // if it ends in / just append the name
    if (outputPath[outputPath.size()-1] == '/')
      {
      m_OutputFileName << outputPath << m_NameWithIndex.str();
      }
    // otherwise, add / and the name
    else
      {
      m_OutputFileName << outputPath << "/" << m_NameWithIndex.str();
      }
#endif

    return m_OutputFileName.str();
    }

  static bool
  Write( const std::string &filePrefix,
         std::string &outputPath,
         bool ascii )
    {
    try
      {
      ++m_CallNumber;

      using PixelType = TPixelType;
      using ImageType = itk::Image<PixelType,VImageDimension>;


      // force use of VTKImageIO
      using IOType = itk::VTKImageIO;
      IOType::Pointer vtkIO = IOType::New();
      if (ascii)
        {
        vtkIO->SetFileTypeToASCII();
        }
      else
        {
        vtkIO->SetFileTypeToBinary();
        }

      using ImageFileWriterType = itk::ImageFileWriter<ImageType>;
      typename ImageFileWriterType::Pointer writer = ImageFileWriterType::New();
      writer->SetImageIO( vtkIO );

      // allocate an 10x10x10 image
      typename ImageType::Pointer image = ImageType::New();
      typename ImageType::SizeType imageSize;
      imageSize.Fill(10);
      image->SetRegions( imageSize );
      image->Allocate();

      unsigned int cnt = 0;
      itk::ImageRegionIterator< ImageType > i( image, image->GetLargestPossibleRegion() );
      i.GoToBegin();
      while (! i.IsAtEnd() )
        {
        // fill the image switching between these pixels
        switch (cnt%4)
          {
          case 0:
            i.Set( itk::NumericTraits<PixelType>::ZeroValue() );
            break;
          case 1:
            i.Set( itk::NumericTraits<PixelType>::OneValue() );
            break;
          case 2:
            i.Set( itk::NumericTraits<PixelType>::OneValue() );
            break;
          case 3:
            i.Set( itk::NumericTraits<PixelType>::ZeroValue() );
          }
        ++cnt;
        ++i;
        }

      writer->SetInput( image );

      std::string m_OutputFileName =
        VTKImageIOTester<char,3>::SetupFileName(filePrefix, "vtk", outputPath);

      writer->SetFileName( m_OutputFileName );
      writer->Update();

      // test the CanWriteFile function after the fact (should always
      // be true at this point)
      if (!vtkIO->CanWriteFile(m_OutputFileName.c_str()))
        {
        return false;
        }

      return true;

      }
    catch (itk::ExceptionObject &e)
      {
      std::cout << e << std::endl;
      return false;
      }
    }

  static bool
  Read( const std::string &filePrefix,
        std::string &outputPath,
        bool ascii )
    {
    try
      {
      using PixelType = TPixelType;
      using ImageType = itk::Image<PixelType,VImageDimension>;

      using ImageFileReaderType = itk::ImageFileReader<ImageType>;
      typename ImageFileReaderType::Pointer reader = ImageFileReaderType::New();

      // force use of VTKImageIO
      using IOType = itk::VTKImageIO;
      IOType::Pointer vtkIO = IOType::New();
      reader->SetImageIO(vtkIO);

      // set ascii or binary
      if (ascii)
        {
        vtkIO->SetFileTypeToASCII();
        }
      else
        {
        vtkIO->SetFileTypeToBinary();
        }

      std::string m_OutputFileName = VTKImageIOTester::SetupFileName( filePrefix, "vtk", outputPath );
      reader->SetFileName( m_OutputFileName );

      // Check that the correct content was written to the header.
      std::ifstream istrm( m_OutputFileName.c_str() );
      char firstline[25];
      istrm.getline( firstline, 24 );
      istrm.close();
      if( strncmp( firstline, "# vtk DataFile Version ", 24 ) != 0 )
        {
        std::cout << "Header string was not written properly." << std::endl;
        return false;
        }

      // read the image
      typename ImageType::Pointer image = reader->GetOutput();
      reader->Update();

      // test the CanReadFile function after the fact (should always
      // be true at this point)
      if (!vtkIO->CanReadFile(m_OutputFileName.c_str()))
        {
        return false;
        }

      // check the size
      typename ImageType::RegionType region =
        image->GetLargestPossibleRegion();
      typename ImageType::SizeType size = region.GetSize();
      bool sizeGood = true;
      for (unsigned int i = 0; i < ImageType::GetImageDimension(); i++)
        {
        if (size[i] != 10)
          {
          sizeGood = false;
          break;
          }
        }
      if (!sizeGood)
        {
        std::cout << "Error: Size didn't read properly" << std::endl;
        return false;
        }

      // check each pixel
      bool pixelsGood = true;
      unsigned int cnt = 0;
      itk::ImageRegionIterator< ImageType > iter( image, region );
      iter.GoToBegin();

      while (! iter.IsAtEnd() && pixelsGood)
        {
        // check image switching between these pixels
        switch (cnt%4)
          {
          case 0:
// Comparison with complex???
            if (itk::Math::NotExactlyEquals(iter.Get(), itk::NumericTraits<PixelType>::ZeroValue()))
              {
              pixelsGood = false;
              }
            break;
          case 1:
            if (itk::Math::NotExactlyEquals(iter.Get(), itk::NumericTraits<PixelType>::OneValue()))
              {
              pixelsGood = false;
              }
            break;
          case 2:
            if (itk::Math::NotExactlyEquals(iter.Get(), itk::NumericTraits<PixelType>::OneValue()))
              {
              pixelsGood = false;
              }
            break;
          case 3:
            if (itk::Math::NotExactlyEquals(iter.Get(), itk::NumericTraits<PixelType>::ZeroValue()))
              {
              pixelsGood = false;
              }
            break;
          }
        ++cnt;
        ++iter;
        }

      if (!pixelsGood)
        {
        std::cout << "Error: Pixels didn't read properly" << std::endl;
        return false;
        }

      // reading successful, so return true
      return true;
      }
    catch(itk::ExceptionObject &e)
      {
      std::cout << e << std::endl;
      return false;
      }
    }

  static bool
  CanReadFileTest( const std::string &filePrefix,
                   const std::string &fileExtension,
                   std::string &outputPath )
    {
    using IOType = itk::VTKImageIO;
    IOType::Pointer vtkIO = IOType::New();

    std::string fileName =
      VTKImageIOTester::SetupFileName(filePrefix, fileExtension, outputPath);

    return vtkIO->CanReadFile( fileName.c_str() );
    }

  static bool
  CanWriteFileTest( const std::string &filePrefix,
                    const std::string &fileExtension,
                    std::string &outputPath )
    {
    using IOType = itk::VTKImageIO;
    IOType::Pointer vtkIO = IOType::New();

    std::string fileName =
      VTKImageIOTester::SetupFileName(filePrefix, fileExtension, outputPath);

    return vtkIO->CanWriteFile( fileName.c_str() );
    }
};

template<typename TScalar>
int Test1AsciiBinary(std::string filePrefix, std::string outputPath,
    std::string typeName, bool ascii, bool read = true)
{
  std::string ab;
  if (ascii)
    {
    ab = "ascii";
    }
  else
    {
    ab = "binary";
    }

  if (!(VTKImageIOTester<TScalar, 3>::Write( filePrefix, outputPath, true )))
    {
    std::cout << "[FAILED] writing " << typeName << " - " << ab << std::endl;
    return EXIT_FAILURE;
    }
  std::cout << "[PASSED] writing " << typeName << " - " << ab << std::endl;

  if (!read)
    {
    return EXIT_SUCCESS;
    }

  if (!(VTKImageIOTester<TScalar, 3>::Read( filePrefix, outputPath, true )))
    {
    std::cout << "[FAILED] reading " << typeName << " - " << ab << std::endl;
    return EXIT_FAILURE;
    }
  std::cout << "[PASSED] reading " << typeName << " - " << ab << std::endl;

  return EXIT_SUCCESS;
}


template<typename TScalar>
int Test1Type(std::string filePrefix, std::string outputPath,
    std::string typeName, bool read = true)
{
  int status = 0;
  status += Test1AsciiBinary<TScalar>(filePrefix, outputPath, typeName, true, read);
  status += Test1AsciiBinary<TScalar>(filePrefix, outputPath, typeName, false, read);
  return status;
}

int itkVTKImageIO2Test(int argc, char* argv[])
{

  if( argc < 2 )
    {
    std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << " outputPath" << std::endl;
    return EXIT_FAILURE;
    }

  std::string filePrefix = argv[0];
  std::string outputPath = argv[1];

  //
  // test all usable pixel types
  //

  int status = 0;

  status += Test1Type<unsigned char>(filePrefix, outputPath, "unsigned char");
  status += Test1Type<char>(filePrefix, outputPath, "char");
  status += Test1Type<unsigned short>(filePrefix, outputPath, "unsigned short");
  status += Test1Type<short>(filePrefix, outputPath, "short");
  status += Test1Type<unsigned int>(filePrefix, outputPath, "unsigned int");
  status += Test1Type<int>(filePrefix, outputPath, "int");
  status += Test1Type<unsigned long>(filePrefix, outputPath, "unsigned long");
  status += Test1Type<long>(filePrefix, outputPath, "long");
  status += Test1Type<unsigned long long>(filePrefix, outputPath, "unsigned long long");
  status += Test1Type<long long>(filePrefix, outputPath, "long long");

  status += Test1Type<float>(filePrefix, outputPath, "float");
  status += Test1Type<double>(filePrefix, outputPath, "double");
  status += Test1Type<itk::RGBPixel<unsigned char> >(filePrefix, outputPath, "RGBPixel<unsigned char>");
  status += Test1Type<itk::RGBAPixel<unsigned char> >(filePrefix, outputPath, "RGBAPixel<unsigned char>");
  status += Test1Type<itk::Vector<int, TEST_VECTOR_PIXEL_DIM> >(filePrefix, outputPath, "Vector<int>");
  status += Test1Type<itk::Vector<double, TEST_VECTOR_PIXEL_DIM> >(filePrefix, outputPath, "Vector<double>");
  status += Test1Type<itk::SymmetricSecondRankTensor<double, 3> >(filePrefix, outputPath, "SymmetricSecondRankTensor<double, 3>");
  std::cout << "Writing a 2 dimension tensor is possible, but reading is not." << std::endl;
  status += Test1Type<itk::SymmetricSecondRankTensor<double, 2> >(filePrefix, outputPath, "SymmetricSecondRankTensor<double, 2>", false);


  //
  // Test bad paths
  //

  // read bad file extension
  if ( VTKImageIOTester<char,3>::CanReadFileTest( filePrefix, "bad", outputPath ))
    {
    std::cout << "[FAILED] didn't properly reject bad file extension for reading" << std::endl;
    status++;
    }
  std::cout << "[PASSED] rejected bad file extension for reading" << std::endl;

  // read bad file name
  if ( VTKImageIOTester<char,3>::CanReadFileTest( "BadFile", "vtk", outputPath ))
    {
    std::cout << "[FAILED] didn't properly reject bad file name for reading" << std::endl;
    status++;
    }
  std::cout << "[PASSED] rejected bad file name for reading" << std::endl;

  // write bad file extension
  if ( VTKImageIOTester<char,3>::CanWriteFileTest( filePrefix, "bad", outputPath ))
    {
    std::cout << "[FAILED] didn't properly reject bad file extension for writing" << std::endl;
    status++;
    }
  std::cout << "[PASSED] rejected bad file extension for writing" << std::endl;

  // write bad file extension when the string ".vtk" is part of the prefix
  if ( VTKImageIOTester<char,3>::CanWriteFileTest( filePrefix + ".vtk", "bad", outputPath ))
    {
    std::cout << "[FAILED] didn't properly reject bad file extension for writing" << std::endl;
    status++;
    }
  std::cout << "[PASSED] rejected bad file extension for writing" << std::endl;

  //
  // use print methods
  //
  using IOType = itk::VTKImageIO;
  IOType::Pointer vtkIO = IOType::New();

  ITK_EXERCISE_BASIC_OBJECT_METHODS( vtkIO, VTKImageIO, StreamingImageIOBase );

  std::cout << "All tests finished!" << std::endl;
  return status;
}
